Skip to content

Adding a New Service

Repeatable pattern for adding a new self-hosted service to the homelab. Based on the Phase 1 pattern (n8n).

Last updated: 2026-04-10


Prerequisites

  • Service has a Docker image
  • You have a subdomain in mind (e.g., app.exzentcg.com)
  • You know what port the service listens on

Step 1 — Create the LXC

From Proxmox WebUI → Create CT:

Setting Value
CT ID Next available (103, 104, ...)
Hostname <service-name>
Template debian-12-standard
Root disk 8 GB (adjust per service)
CPU 1 core
Memory 512–1024 MB (adjust per service)
Network Bridge: vmbr0, Static IP: 192.168.0.<next>/24, Gateway: 192.168.0.1
DNS 192.168.0.1
Features Nesting ✅ (required for Docker)

Check Start after created → Finish.


Step 2 — Install Docker and deploy

# SSH or console into the new LXC
apt update && apt upgrade -y
apt install curl -y
curl -fsSL https://get.docker.com | sh

# Create service directory
mkdir -p /opt/<service-name> && cd /opt/<service-name>

# Create docker-compose.yml
nano docker-compose.yml

# Create .env if needed
nano .env
chmod 600 .env

# Start
docker compose up -d
docker compose ps
docker compose logs --tail 30 <service>

Step 3 — Configure LXC firewall

From the Proxmox host, create /etc/pve/firewall/<CTID>.fw:

cat > /etc/pve/firewall/<CTID>.fw << 'EOF'
[OPTIONS]
enable: 1

[RULES]
IN ACCEPT -source +edge_gw -p tcp -dport <SERVICE_PORT>   # Only edge-gateway can reach it
IN ACCEPT -source +admin_desktop -p tcp -dport 22          # SSH from admin
IN DROP                                                     # Block everything else
OUT ACCEPT -dest +router_gw -p udp -dport 53               # DNS
OUT ACCEPT -dest +router_gw -p tcp -dport 53               # DNS
OUT DROP -dest +lan_subnet                                  # Lateral movement prevention
OUT ACCEPT -p tcp -dport 443                                # HTTPS APIs (if needed)
EOF

Enable firewall on the container: Proxmox WebUI → CT → Firewall → Options → Firewall: Yes.

Verify:

# From inside the new LXC
curl -s --connect-timeout 3 http://192.168.0.16 -o /dev/null -w "HTTP %{http_code}\n"
# Expected: HTTP 000 (LAN blocked)

curl -s --connect-timeout 3 https://192.168.0.200:8006 -o /dev/null -w "HTTP %{http_code}\n"
# Expected: HTTP 000 (Proxmox blocked)


Step 4 — Update edge-gateway firewall

Add outbound rule for edge-gateway to reach the new service. Edit /etc/pve/firewall/101.fw on the Proxmox host:

OUT ACCEPT -dest 192.168.0.<IP> -p tcp -dport <SERVICE_PORT>  # Proxy to <service-name>

Place this before the OUT DROP -dest +lan_subnet line.


Step 5 — Add NPM proxy host

  1. Open NPM admin panel: http://192.168.0.51:81
  2. Proxy Hosts → Add Proxy Host:
Field Value
Domain Names <subdomain>.exzentcg.com
Scheme http
Forward Hostname / IP 192.168.0.<IP>
Forward Port <SERVICE_PORT>
Websockets Support ✅ if the app uses WebSockets
SSL Certificate None

Step 6 — Add Cloudflare tunnel route

Cloudflare → Zero Trust → Networks → Tunnels → exzentcg-homelab → Published application routes → Add:

Field Value
Subdomain <subdomain>
Domain exzentcg.com
Path (empty)
Service Type HTTP
URL localhost:80
HTTP Host Header <subdomain>.exzentcg.com

Cloudflare auto-creates the DNS CNAME (proxied).


Step 7 — Add Cloudflare Access policy

Zero Trust → Access → Applications → Add → Self-hosted:

Field Value
Application name <service-name>
Subdomain <subdomain>
Domain exzentcg.com
Session Duration 24 hours

Add policy: - Name: owner-only (reuse existing) - Action: Allow - Selector: Emails → exzensg@gmail.com

If the service needs unauthenticated webhook endpoints, create a second application with a path prefix and a Bypass + Everyone policy (same pattern as n8n-webhooks).


Step 8 — Test end-to-end

  1. Visit https://<subdomain>.exzentcg.com
  2. Cloudflare Access login should appear
  3. Log in → service should load
  4. Test from another device / incognito to confirm access control

Step 9 — Snapshot

pct snapshot <CTID> initial-deploy --description "<service-name> initial deployment"

Step 10 — Document

  1. Add credentials to 00_secrets/Credentials Index.md
  2. Create 02_setup_logs/Phase <N> Actions.md with deployment notes
  3. Update 01_planning/Architecture Diagram.md with the new service
  4. Add health check commands to Phase 1 Health Checks.md

Checklist

  • [ ] LXC created with static IP and Docker
  • [ ] Service deployed and running
  • [ ] LXC firewall rules applied and tested
  • [ ] edge-gateway firewall updated to allow proxy
  • [ ] NPM proxy host configured
  • [ ] Cloudflare tunnel route added
  • [ ] Cloudflare Access policy applied
  • [ ] End-to-end test passed
  • [ ] Snapshot taken
  • [ ] Documentation updated